package service

import (
	"context"
	"encoding/json"
	"github.com/gin-gonic/gin"
	batchv1 "k8s.io/api/batch/v1"
	batchv1beta1 "k8s.io/api/batch/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	_ "k8s.io/client-go/kubernetes/typed/batch/v1beta1"
	"kubernate-server/conf"
	"log"
	"net/http"
	"strings"
)

type CronJobService struct{}

type CronJobInfoCreate struct {
	Name          string   `json:"name"`
	Namespace     string   `json:"namespace"`
	ContainerName string   `json:"containername"`
	Image         string   `json:"image"`
	Command       []string `json:"command"`
	Schedule      string   `json:"schedule"`
}

type CronJobInfoSelect struct {
	Name          string   `json:"name"`
	Namespace     string   `json:"namespace"`
	ContainerName string   `json:"containername"`
	Image         string   `json:"image"`
	Command       []string `json:"command"`
	Schedule      string   `json:"schedule"`
	Status        string   `json:"status"`
}

type CronJobUpdate struct {
	Namespace string  `json:"namespace"`
	Schedule  *string `json:"schedule"` // 指针
	Image     *string `json:"image"`
	Name      string  `json:"name"` // 必须大写
}

/*
*
* Author: 杨旭
* Description: 创建CJ
* Date:

	{
	 "name":"test1234",
	 "containername":"c2",
	 "image":"registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v2",
	 "Command": ["/bin/sh", "-c", "echo Hello from CronJob && date"]
	}
*/
func (CronJobService) CronJobCreate(ctx *gin.Context) {
	var cj_info CronJobInfoCreate // 1. 声明变量
	/*
		ctx:这次 HTTP请求的“上下文”，里面装着 Postman 发过来的 JSON
		ShouldBindJSON(&cj_info):把 JSON数据塞进cj_info这个变量里；成功返回 nil，失败返回错误
		err :=  把错误先接住，方便后面判断
		if err ... != nil 	err不为nil（类似java的null）就是有错误，此时如果真有错误（比如 JSON 格式不对、缺字段），就进括号处理
	*/
	if err := ctx.ShouldBindJSON(&cj_info); err != nil { // 2. 把请求体绑到 cj_info
		/*
			    有错误进if
				http.StatusBadRequest	失败返回状态码400，告诉 Postman“你传的东西有问题”
				gin.H{...}	快速拼一个 map：{"error":"具体错误信息"}
				ctx.JSON(...)	把 map 转成JSON字符串写回给 Postman
		*/
		ctx.JSON(20000, gin.H{"error": err.Error()})
		return
	}
	/*
		batchv1beta1.CronJob	使用“batchv1beta1”版本下的 CronJob 类型（K8s 1.21+）
		&	取地址——构造完返回的是指针，后面传给 ClientSet 要用指针
		类似于java batchv1beta1.CronJob返回一个对象叫cj
	*/
	// 之所以写&batchv1beta1要写& 因为batchv1beta1整个对象要当成指针传给别人
	cj := &batchv1beta1.CronJob{
		/*
			ObjectMeta	K8s资源“户口本”字段：名字、命名空间、标签、注解等都放这里
			metav1.ObjectMeta	对应包k8s.io/apimachinery/pkg/apis/meta/v1里的结构体
		*/
		ObjectMeta: metav1.ObjectMeta{
			/*
				Name	CronJob在集群里的唯一名字，后面用 kubectl get cj <这里> 就能查到
				cj_info.Name	我们前面把 Postman JSON 里的 "name" 字段装进 cj_info，现在拿出来用
			*/
			//Name:      "demo-cron", 硬编码
			// cj名称
			Name: cj_info.Name, // 来自Postman传值
			// 名称空间
			Namespace: cj_info.Namespace,
		},
		// 这里使用batchv1beta1指针调用CronJobSpec
		Spec: batchv1beta1.CronJobSpec{
			Schedule: cj_info.Schedule,
			JobTemplate: batchv1beta1.JobTemplateSpec{
				Spec: batchv1.JobSpec{
					Template: corev1.PodTemplateSpec{
						Spec: corev1.PodSpec{
							RestartPolicy: corev1.RestartPolicyOnFailure,
							Containers: []corev1.Container{{
								Name:  cj_info.ContainerName,
								Image: cj_info.Image,
								//Command: []string{"/bin/sh", "-c", "echo Hello from CronJob && date"},硬不编码
								/*
									postman传参
									{
									  "command": ["/bin/sh", "-c", "echo Hello from CronJob && date"]
									}
								*/
								Command: cj_info.Command,
							}},
						},
					},
				},
			},
		},
	}
	/*
	   conf.KuberConfigSet	全局变量，里面装着 kubernetes.Clientset，已初始化好   这个是global.go里面KuberConfigSet *kubernetes.Clientset定义的
	   .batchv1beta1()	切换到 batchv1beta1 API 分组
	   .CronJobs("default")	操作 default 命名空间下的 CronJob 资源
	   .Create(...)	真正向 K8s 发起“创建”请求（REST POST）
	   context.TODO()	暂时用空的上下文（没有超时/取消）
	   cj	前面拼好的 CronJob 对象指针
	   metav1.CreateOptions{}	创建时的附加选项（如 DryRun、字段验证），这里留空
	   _, err	返回值里我们只关心是否报错，创建成功返回的对象用 【_】 丢弃
	*/
	_, err := conf.KuberConfigSet.BatchV1beta1().CronJobs(cj_info.Namespace).
		Create(context.TODO(), cj, metav1.CreateOptions{})
	// 如果 K8s 返回任何错误（资源已存在、权限不足、格式不对等）就进分支
	if err != nil {
		/*
			http.StatusOK	成功返回状态码200，业务层自己决定成功/失败，所以 HTTP 码仍 200
			gin.H{...}	快速拼 JSON：{"message":"failure","error":"具体错误"}
			err.Error()	把 Go 的 error 转成字符串给前端看
		*/
		ctx.JSON(http.StatusOK, gin.H{"message": "failure", "error": err.Error()})
		return
	}
	ctx.JSON(http.StatusOK, gin.H{
		"code":   20000,
		"messge": "success",
	})
}

/*
*
* Author: 杨旭
* Description: 删除CJ
* Date:

	{
	 "name":"test1234",
	 "namespace":"default"
	}
*/
func (CronJobService) CronJobDelete(ctx *gin.Context) {
	var cj_info CronJobInfoCreate
	if err := ctx.ShouldBindJSON(&cj_info); err != nil { // 2. 把请求体绑到 cj_info
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	err := conf.KuberConfigSet.BatchV1beta1().CronJobs(cj_info.Namespace).
		Delete(context.TODO(), cj_info.Name, metav1.DeleteOptions{})
	if err != nil {
		ctx.JSON(http.StatusOK, gin.H{"message": "failure", "error": err.Error()})
		return
	}
	ctx.JSON(http.StatusOK, gin.H{
		"code":   20000,
		"messge": "success",
	})
}

/*
*
* Author: 杨旭
* Description: 查看CJ
* Date:
没有参数
*/
func (CronJobService) CronJobSelect(ctx *gin.Context) {

	// 支持指定命名空间
	// 从请求参数获取ns 名称空间
	ns := ctx.Param("namespace")
	if strings.Contains("", ns) {
		ns = "default"
	}
	// 1. 取列表
	// 返回*batchv1beta1.CronJobList结构，给到cjList这是一个列表对象
	cjList, err := conf.KuberConfigSet.BatchV1beta1().CronJobs(ns).
		List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		ctx.JSON(http.StatusOK, gin.H{"message": "failure", "error": err.Error()})
		return
	}

	// 2. 按需裁剪字段（防止返回过大）
	// 预分配切片，长度 0，容量等于 CronJob 条数，相当于java新建list对象设置长度一样
	resp := make([]CronJobInfoSelect, 0, len(cjList.Items))
	/*
		cjList.Items 就是 []batchv1beta1.CronJob 类似于for循环list，每次从list取出一个对象赋给cj
		每次迭代 cj 是单个 CronJob 对象（值拷贝，只读即可）
	*/
	for _, cj := range cjList.Items {
		status := ""
		if strings.Contains(cj.Status.String(), "Active") {
			status = "active"
		}
		/*
			resp	目标切片，类型 []CronJobInfo，最初是空的（长度 0）
			append(切片, 新元素)	把右边这个新结构体 追加 到左边切片末尾
			返回值重新赋给 resp	Go 切片底层可能重新分配内存，append 返回新的切片头，必须接住
			类似于java，resp是个list，for循环里面set值到dto，append类似于将每次set完的dto，进行list.add操作写进list
		*/
		resp = append(resp, CronJobInfoSelect{
			Name:      cj.Name,
			Namespace: cj.Namespace,
			Schedule:  cj.Spec.Schedule,
			Image:     cj.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image,
			Command:   cj.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command,
			Status:    status,
		})
	}
	result := ""
	if len(resp) == 0 {
		result = "当前名称空间，没有符合结果的cj控制器"
	} else {
		result = "查询成功"
	}

	// 统一返回
	ctx.JSON(http.StatusOK, gin.H{
		"message": result,
		"total":   len(resp),
		"items":   resp,
		"code":    20000,
	})
}

/*
*
* Author: 杨旭
* Description: 修改CJ
* Date:
 */
func (CronJobService) CronJobUpdate(ctx *gin.Context) {
	var patchData CronJobUpdate

	if err := ctx.ShouldBindJSON(&patchData); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 1. 先拿到现有对象（自动带出容器名）
	oldCJ, err := conf.KuberConfigSet.BatchV1beta1().CronJobs(patchData.Namespace).
		Get(context.TODO(), patchData.Name, metav1.GetOptions{})
	if err != nil { // 这里必须有 if
		ctx.JSON(http.StatusOK, gin.H{"message": "failure", "error": err.Error()})
		return
	}

	// 2. 只拼非空字段
	//update := make(map[string]interface{})
	//if patchData.Schedule != nil {
	//	update["spec"] = map[string]interface{}{
	//		"schedule": *patchData.Schedule,
	//	}
	//}
	patch := make(map[string]interface{})
	if patchData.Schedule != nil {
		patch["spec"] = make(map[string]interface{})
		patch["spec"].(map[string]interface{})["schedule"] = *patchData.Schedule
	}

	if patchData.Image != nil {
		containerName := oldCJ.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Name
		jobTpl := map[string]interface{}{
			"spec": map[string]interface{}{
				"template": map[string]interface{}{
					"spec": map[string]interface{}{
						"restartPolicy": "OnFailure",
						"containers": []map[string]interface{}{{
							"name":  containerName,
							"image": *patchData.Image,
						}},
					},
				},
			},
		}
		// 如果前面已经有 spec，就复用；否则新建
		if _, ok := patch["spec"]; !ok {
			patch["spec"] = make(map[string]interface{})
		}
		patch["spec"].(map[string]interface{})["jobTemplate"] = jobTpl
	}

	// 3. 统一发出去
	payload, _ := json.Marshal(patch)
	log.Printf("[debug] merged patch: %s", payload)

	_, err = conf.KuberConfigSet.BatchV1beta1().CronJobs(patchData.Namespace).
		Patch(context.TODO(), patchData.Name, types.MergePatchType, payload, metav1.PatchOptions{})
	if err != nil {
		ctx.JSON(http.StatusOK, gin.H{"message": "failure", "error": err.Error()})
		return
	}

	// 如果 K8s 返回任何错误（资源已存在、权限不足、格式不对等）就进分支
	if err != nil {
		/*
			http.StatusOK	成功返回状态码200，业务层自己决定成功/失败，所以 HTTP 码仍 200
			gin.H{...}	快速拼 JSON：{"message":"failure","error":"具体错误"}
			err.Error()	把 Go 的 error 转成字符串给前端看
		*/
		ctx.JSON(http.StatusOK, gin.H{"message": "failure", "error": err.Error()})
		return
	}
	ctx.JSON(http.StatusOK, gin.H{
		"code":   20000,
		"messge": "success",
	})
}
